/**
 *	---------------------------------------------------------------------------
 *	COMP 6231/FALL 2013
 *	Assignment - 02
 *	@author Talha Rahman (1296884)
 *	---------------------------------------------------------------------------
 *	This class is an abstraction for the data structures to store tokens.
 *  The class has a public member called recordStorage which is a list of
 *  ConcurrentHashMap's. This is to maximize concurrency - for each letter 
 *  of the last name there is a ConcurrentHashMap - the other ConcurrentHashMaps
 *  need not be locked when one is being updated.
 *  	  
 */
package ca.concordia.comp6231.record;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

/**
 * This class implements a database of records which is thread-safe and 
 * maximizes concurrency. The records are stored in a List of ConcurrentHashMaps. 
 * Each item in a list contains records that have has a last name which begins with
 * the corresponding position of the letter in the alphabet (i.e. list item with index 
 * zero has last names beginning with 'A', list item one with 'B', etc.). As lists are
 * not serial, but ConcurrentHashMaps are - so concurrency and thread safety both are 
 * desired criteria are ensured.  
 */
public class RecordStore {
	
	private final List< ConcurrentHashMap<String, Record> > recordStorage;
	
	/**
	 * Constructor
	 */
	public RecordStore(){
		recordStorage = new ArrayList< ConcurrentHashMap<String, Record> >();
		
		for(int i = 0; i < 26 ; ++i){
			recordStorage.add(new ConcurrentHashMap<String,Record>());
		}
	}
	
	/**
	 * Adds a new record to the store.
	 * @param firstLetterOfLastName The first letter of the last name of the record.
	 * @param record The record.
	 * @throws Exception
	 */
	public void addNewRecord(char firstLetterOfLastName, Record record) throws Exception{
		int index = getIndexFromLetter(firstLetterOfLastName);
		ConcurrentHashMap<String, Record> mapAtIndex = recordStorage.get(index);
		
		if(mapAtIndex.contains(record.getRecordID())){
			throw new Exception("Record with this ID already exists." + record);
		}
		
		mapAtIndex.putIfAbsent(record.getRecordID(), record);
	}
	
	/**
	 * Edits a record (change status).
	 * @param recordID The record ID.
	 * @param lastName The last name of the record.
	 * @param newStatus The new status of the record.
	 */
	public void editRecord(String recordID, String lastName, String newStatus){
		char firstLetterOfLastName = lastName.substring(0, 1).toUpperCase().charAt(0);
		int index = getIndexFromLetter(firstLetterOfLastName);
		ConcurrentHashMap<String, Record> mapAtIndex = recordStorage.get(index);
		
		Record record = mapAtIndex.get(recordID);
		synchronized (record) {
			if(record != null){
				record.setStatus(newStatus);
			}
		}
	}
	
	/**
	 * Looks up a record by last name and record ID.
	 * @param lastName The last name of the record.
	 * @param recordID The record ID.
	 * @return The record if found (else returns null).
	 */
	public Record findRecordByLastNameAndID(String lastName, String recordID){
		char firstLetterOfLastName = lastName.substring(0, 1).toUpperCase().charAt(0);
		int index = getIndexFromLetter(firstLetterOfLastName);
		ConcurrentHashMap<String, Record> mapAtIndex = recordStorage.get(index);
		Record record = mapAtIndex.get(recordID);
		
		if(record.getLastName().equals(lastName)){
			return record;
		}
		else{
			return null;
		}
	}
	
	/**
	 * Finds a record by ID
	 * @param recordID The record ID to search for.
	 * @return The retrieved record - returns null if record with specified ID not found.
	 */
	public Record findRecordByID(String recordID){
		
		for(ConcurrentHashMap<String,Record> map : recordStorage){
			if(map.containsKey(recordID)){
				return map.get(recordID);
			}
		}
		
		return null;
	}
	
	/**
	 * Deletes a record by ID.
	 * @param recordID The ID of the record to delete.
	 */
	public String deleteRecordById(String recordID){
		Record record = findRecordByID(recordID);
		if(record == null){
			return "Record not found with ID : " + recordID;
		}
		
		String lastName = record.getLastName();
		char firstLetterOfLastName = lastName.substring(0, 1).toUpperCase().charAt(0);
		int index = getIndexFromLetter(firstLetterOfLastName);
		ConcurrentHashMap<String, Record> mapAtIndex = recordStorage.get(index);
		synchronized (mapAtIndex) {
			mapAtIndex.remove(recordID);
		}
		
		return "Deleted record with ID : " + recordID;
	}
	
	/**
	 * Prints all the records in the store to the screen.
	 */
	public void printAllRecords(){
		int index = 0, total = 0;
		for(ConcurrentHashMap<String, Record> map:recordStorage){
			System.out.print(getLetterFromIndex(index) + " : {");
			for(Entry<String, Record> e : map.entrySet()){
				System.out.println(e.getKey() + "=" + e.getValue());
				++total;
			}
			System.out.print("}");
			System.out.println("");
			++index;
		}
		
		System.out.println("Total no : " + total);
	}
	
	/**
	 * Counts the no total no. of records.
	 * @return The total no of records in the store.
	 */
	public int getRecordCount(){
		int total = 0;
		for(ConcurrentHashMap<String, Record> map:recordStorage){
			for(Entry<String, Record> e : map.entrySet()){
				++total;
			}
		}
		
		return total;
	}
	
	/**
	 * Gets the numeric index of a letter based on it's position in the alphabet. 
	 * @param letter The letter
	 * @return The index.
	 */
	private static int getIndexFromLetter(char letter){
		return (int)(letter - 'A');
	}
	
	/**
	 * Gets the letter from the supplied index based on it's position in the alphabet.
	 * @param index The index.
	 * @return The letter.
	 */
	private static char getLetterFromIndex(int index){
		return (char)(65 + index);
	}

}
